package net.ib.mtalk.network;

import net.ib.mtalk.util.MTalkLogWriter;
import android.os.Handler;

/**
 * 네트워크 콜백 구조의 구현.<br>
 * 이 클래스의 멤버가 main thread에서 선언되고, Envelope 대응 클래스 등에 콜백으로 넘겨져야 한다.<br>
 * 
 * <pre>
 * Class implements NetResultReceiver.Receiver {
 * 	onCreate() {
 * 		mCallback = new NetResultReceiver(new Handler(), this);
 * 	}
 * 
 * 	func() {
 * 		Revelope rev = new Revelope.builder()
 * 			.setA().setB().setCallback(mCallback).build();
 * 		NetClient.call(rev);
 * 	}
 * 
 * 	Override
 * 	void onNetResult(int resultCode, Object resultData) {
 * 		eat(resultData);
 * 	}
 * }</pre>
 * 
 * @see {@link NetResultReceiver.Receiver} - 구현해야 하는 콜백 함수의 인터페이스
 */
public class NetResultReceiver {
	
	public static final int NONE	= -1;
	/**
	 * HTTP 요청의 결과임을 나타냄.
	 */
	public static final int HTTP_RESULT	= 0;
	/**
	 * TCP 요청의 결과임을 나타냄.
	 */
	public static final int TCP_RESULT	= 1;

	/**
	 * call back 을 돌려줄 Thread 에 대한 Handler
	 */
	final Handler mHandler;
	
	/**
	 * 돌려 줄 mCallback 에 대한 정보</br>
	 * Constructor, setReceiver 를 통해 정의 돼야 한다.
	 * 
	 */
	private NetResultReceiver.Receiver mReceiver;

	/**
	 * 임의의 시점에 이 콜백을 무효화하기 위해서 세팅하는 플래그.
	 */
	private boolean recalled = false;

	/**
	 * mCallback 을 넘겨받을 Thread 에 대한 handler 를 전달한다. 
	 * @param handler
	 */
	public NetResultReceiver(Handler handler) {
		mHandler = handler;
	}

	/**
	 * mCallback 을 넘겨받을 Thread 의 handler 와 mCallback interface 에 대해 전달한다.
	 * @param handler
	 * @param receiver
	 */
	public NetResultReceiver(Handler handler, NetResultReceiver.Receiver receiver) {
		mHandler = handler;
		setReceiver(receiver);
	}
	
	/**
	 * 처음 만들어진 객체는 항상 유효하다.
	 * 그러나 {@link #recall()}이 호출된 이후로는 {@link Receiver#onNetResult(int, Object, Object)}, {@link Receiver#onNetError(int, Object)}는 호출되지 않는다.
	 * @return 리콜되어 무효라면 true. 유효하면 false.
	 */
	synchronized public boolean isRecalled() {
		return recalled;
	}
	
	/**
	 * '네트워크 모듈이 이 콜백의 존재 자체를 무시하게' 만든다.
	 * 이 메소드를 사용하면, 네트워크로부터 응답이 와도 아무런 콜백 함수도 불리지 않는다.
	 * 이 콜백 객체가 전송 기록에서 그냥 사라진 것처럼, 혹은 애초에 네트워크 요청이 없었던 것처럼 행동할 것이다.
	 * @see #isRecalled()
	 */
	synchronized public void recall() {
		recalled = true;
	}
	
	/**
	 * mCallback interface 에 대해 전달한다.
	 * @param receiver
	 */
	public void setReceiver(NetResultReceiver.Receiver receiver) {
		mReceiver = receiver;
	}
	
	/**
	 * Callback interface 정의
	 * 콜백 객체는 이 인터페이스를 implement해야 함.
	 * @author namkhoh
	 */
	public interface Receiver {
		/**
		 * @param resultCode 결과가 무슨 타입인지 구분하기 위해서.
		 * {@link NetResultReceiver#HTTP_RESULT}, {@link NetResultReceiver#HTTP_RESULT}.
		 * @param sentObject 이전에 전송을 요청했던 객체
		 * @param receivedObject 결과의 객체
		 */
		public void onNetResult(int resultCode, Object sentObject, Object receivedObject);
		/**
		 * 네트워크 에러로 전송에 실패한 경우
		 * @param resultCode 결과가 무슨 타입인지 구분하기 위해서.
		 * {@link NetResultReceiver#HTTP_RESULT}, {@link NetResultReceiver#HTTP_RESULT}.
		 * @param sentObject 요청의 객체를 그대로 돌려줌
		 */
		public void onNetError(int resultCode, Object sentObject);
	}
	
	/**
	 * 지정된 handler 로 Error 를 전달한다.
	 * @param resultCode 결과가 무슨 타입인지 구분하기 위해서.
	 * {@link NetResultReceiver#HTTP_RESULT}, {@link NetResultReceiver#HTTP_RESULT} 등
	 * @param sentObject 결과의 객체
	 */
	synchronized public void passError(int resultCode, Object sentObject) {
		if (! recalled) {
			mHandler.post(new ErrorRunnable(resultCode, sentObject));
		} else {
			MTalkLogWriter.w("NetResultReceiver.passError(): recalled object");
		}
	}
	
	/**
	 * 지정된 handler 로 return 을 전달한다.
	 * @param resultCode 결과가 무슨 타입인지 구분하기 위해서.
	 * {@link NetResultReceiver#HTTP_RESULT}, {@link NetResultReceiver#HTTP_RESULT} 등
	 * @param sentObject 이전에 전송을 요청했던 객체
	 * @param receivedObject 결과의 객체
	 */
	synchronized public void passResult(int resultCode, Object sentObject, Object receivedObject) {
		if (! recalled) {
			mHandler.post(new ResultRunnable(resultCode, sentObject, receivedObject));
		} else {
			MTalkLogWriter.w("NetResultReceiver.passResult(): recalled object");
		}
	}
	
	/**
	 * Handler 로 전달돼 지정된 caller thread 에서 실행되는 부분
	 * @author namkhoh
	 *
	 */
	class ErrorRunnable implements Runnable {

		final int mResultCode;
		final Object mSentObject;
		
		public ErrorRunnable(int resultCode, Object sentObject) {
			mResultCode = resultCode;
			mSentObject = sentObject;
		}
		
		@Override
		public void run() {
			onErrorResult(mResultCode, mSentObject);			
		}
	}
	
	/**
	 * Handler 로 전달돼 지정된 caller thread 에서 실행되는 부분
	 * @author namkhoh
	 *
	 */
	class ResultRunnable implements Runnable {

		final int mResultCode;
		final Object mSentObject;
		final Object mReceivedObject;
		
		public ResultRunnable(int resultCode, Object sentObject, Object receivedObject) {
			mResultCode = resultCode;
			mSentObject = sentObject;
			mReceivedObject = receivedObject;
		}
		
		@Override
		public void run() {
			onReceiveResult(mResultCode, mSentObject, mReceivedObject);
		}
	}
	
	/**
	 * Caller Thread 에서 수행되는 부분
	 * return 전달 
	 */
	protected void onReceiveResult(int resultCode, Object sentObject, Object receivedObject) {
		if (mReceiver != null) {
			mReceiver.onNetResult(resultCode, sentObject, receivedObject);
		}
	}
	
	/**
	 * Caller Thread 에서 수행되는 부분
	 * error 를 알린다.
	 */
	protected void onErrorResult(int resultCode, Object sentObject) {
		if (mReceiver != null) {
			mReceiver.onNetError(resultCode, sentObject);
		}
	}

}
